<?php
/**
 * In dieser Datei wird die Klasse '\UF\IO\ZipArchive' definiert.
 *
 * Aller Code wurde in der Unicode-Kodierung 'utf-8' verfast.
 *
 * @category   UniKap-Framework
 * @package    IO
 * @subpackage Core
 * @author     Ulf -UniKado- Kadner <ulfikado@gmail.com>
 * @since      2014-03-29 16:17
 * @version    0.1
 */

namespace UF\IO
{

    /**
     * A static helper class for easier compress to or decompress from ZIP archive files.
     *
     * @since  v0.1
     */
    class ZipArchive
    {

        # <editor-fold defaultstate="collapsed" desc="- - - >   H I D D E N   C O N S T R U C T O R   - - - - - - - - - - - - - - - - -">

        private function __construct() { }

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="- - - -   P U B L I C   S T A T I C   M E T H O D S   - - - - - - - - - - - - - -">

        /**
         * Compress a single file into a ZIP archive file.
         *
         * @param  string $sourceFile The file to compress
         * @param  string $zipFile    The resulting ZIP archive file.
         * @param  string $workingdir A optional working directory. Its only required if not the folder of $sourceFile
         *         should be used as it.
         * @throws \UF\MissingExtensionException If PHPs Zip support is not enabled (class ZipArchive do not exist)
         * @throws \UF\IO\FileAlreadyExistsException if resulting ZIP archive file already exists.
         * @throws \UF\IO\FileAccessException
         * @throws \UF\IO\Exception
         * @uses   \ZipArchive PHP ZIP support is required to use this method!
         * @since  v0.1
         */
        public static function Zip( $sourceFile, $zipFile, $workingdir=null )
        {
            self::checkZipSupport( '\\UF\\IO\\File::Zip()' );
            $owd = \UF\IO\Path::Unixize( \getcwd() );
            if ( empty( $workingdir ) ) { $workingdir = \UF\IO\Path::Unixize( \dirname( $sourceFile ) ); }
            else { $workingdir = \UF\IO\Path::Unixize( $workingdir ); }
            if ( $workingdir == $owd ) { $owd = null; }
            else { \chdir( $workingdir ); }
            $sourceFile = \preg_replace(
                '~^' . \preg_quote( $workingdir, '~' ) . '/~', '', \UF\IO\Path::Unixize( $sourceFile ) );
            $oldfile = self::zipInitUndo( $zipFile, $owd ); $zip = new \ZipArchive();
            if ( TRUE === ( $res = $zip->open( $zipFile, \ZipArchive::CREATE ) ) ) {
                $zip->addFile( $sourceFile ); $zip->setArchiveComment( 'Archived Single-File' );
                if ( !$zip->close() ) { self::zipUndo( $oldfile, $zipFile, $owd ); }
                if ( !empty( $oldfile ) ) { self::Delete( $oldfile ); }
                if ( !\is_null( $owd ) ) { \chdir( $owd ); } }
            else {
                if ( !\is_null( $owd ) ) { \chdir( $owd ); }
                if ( !empty( $oldfile ) ) { self::Move( $oldfile, $zipFile ); }
                throw new \UF\IO\FileAccessException(
                    $zipFile, \UF\IO\FileAccessException::ACCESS_CREATE,
                    \UF\_( '_io',  'Zipfile could not be created cause ' ) . self::GetZipArchiveError($res) ); }
        }

        /**
         * Crompress a list of files to a single ZIP archive file. It requires that all array keys of $sourcefiles
         * are numeric (the origin file name is uses in archive) or the keys are string, defining the file names
         * to use in archive file
         * genutzt) oder der Key definiert den im Archiv zu nutzenden Dateiname.
         *
         * @param  array  $sourcefiles   The files to compress as a file list
         * @param  string $zipFile       The ZIP archive file to create.
         * @param  string $zipFolderName A optional folder name where the files will be compressed.
         * @throws \UF\MissingExtensionException If PHPs Zip support is not enabled (class ZipArchive do not exist)
         * @throws \UF\IO\FileAlreadyExistsException if resulting ZIP archive file already exists.
         * @throws \UF\IO\FileAccessException
         * @throws \UF\IO\Exception
         * @uses   \ZipArchive PHP ZIP support is required to use this method!
         * @since  v0.1
         */
        public static function ZipList( array $sourcefiles, $zipFile, $zipFolderName = null)
        {
            self::checkZipSupport( '\\UF\\IO\\File::ZipList()' );
            $oldfile = self::zipInitUndo( $zipFile, null );
            $zip = new \ZipArchive();
            if ( FALSE === ( $res = $zip->open( $zipFile, \ZipArchive::CREATE ) ) ) {
                if ( !empty( $oldfile ) ) { self::Move( $oldfile, $zipFile ); }
                throw new \UF\IO\FileAccessException(
                    $zipFile, \UF\IO\FileAccessException::ACCESS_CREATE,
                    \UF\_( '_io',  'Zipfile could not be created cause ' ) . self::GetZipArchiveError($res) ); }
            if ( !empty( $zipFolderName ) ) {
                $zip->addEmptyDir( $zipFolderName );
                $zipFolderName = \rtrim( $zipFolderName, '\\/' ) . '/';
                foreach ( $sourcefiles as $k => $v ) {
                    $key = \is_numeric( $k ) ? \basename( $v ) : $k;
                    $zip->addFile( $v, $zipFolderName . '/' . $key ); } }
            else {
                foreach ( $sourcefiles as $k => $v ) { $zip->addFile( $v, \is_numeric($k) ? \basename( $v ) : $k ); } }
            if ( !$zip->close() )  { self::zipUndo( $oldfile, $zipFile, null ); }
        }

        /**
         * Extracts the contents from defined ZIP archive file to defined target folder.
         *
         * @param  string $zipFile      The Zip archive to extract
         * @param  string $targetFolder Target folder to extract the contents to.
         * @param  bool   $clearTarget  Empty $targetFolder first?
         * @throws \UF\MissingExtensionException If PHPs Zip support is not enabled (class ZipArchive do not exist)
         * @throws \UF\IO\FileNotFoundException If the ZIP file dont exists.
         * @throws \UF\IO\FileAccessException
         * @throws \UF\IO\Exception
         * @uses   \ZipArchive PHP ZIP support is required to use this method!
         * @since  v0.1
         */
        public static function UnZip( $zipFile, $targetFolder, $clearTarget=true )
        {
            self::checkZipSupport( '\\UF\\IO\\File::UnZip()' );
            if ( !\file_exists( $zipFile ) ) { throw new \UF\IO\FileNotFoundException(
                $zipFile, \UF\_( '_io', 'Could not extract from defined archive file. ' ) ); }
            if ( $clearTarget ) { \UF\IO\Folder::MoveContents( $targetFolder, $targetFolder . '-tmp', 0700, true ); }
            $zip = new \ZipArchive();
            if ( \TRUE === ( $res = $zip->open( $zipFile ) ) ) {
                $zip->extractTo( $targetFolder );
                $zip->close(); }
            else {
                if ( !empty( $clearTarget ) ) {
                    \UF\IO\Folder::Move( $targetFolder . '-tmp', $targetFolder, 0700, true ); }
                throw new \UF\IO\FileAccessException(
                    $zipFile, \UF\IO\FileAccessException::ACCESS_READ,
                    \UF\_( '_io', 'Could not read from zip file cause ' ) . self::GetZipArchiveError($res) ); }
        }

        /**
         * Extracts the file with the name $zippedFileName from defined ZIP archive file to defined target file.
         *
         * @param  string $zipFile        The Zip archive to extract
         * @param  string $zippedFileName The name of the file inside the archive, to extract
         * @param  string $targetFile     Extract to this file.
         * @throws \UF\IO\FileNotFoundException If ZIP archive file does not exist.
         * @throws \UF\IO\FileAccessException
         * @uses   \ZipArchive PHP ZIP support is required to use this method!
         * @since  v0.1
         */
        public static function UnZipSingleFile( $zipFile, $zippedFileName,
            $targetFile )
        {
            self::checkZipSupport( '\\UF\\IO\\File::UnZipSingleFile()' );
            try
            {
                \file_put_contents( $targetFile, \file_get_contents( 'zip://' . $zipFile . '#' . $zippedFileName ) );
            }
            catch ( \Exception $ex )
            {
                throw \UF\IO\FileAccessException::Read(
                    $zipFile, \UF\_( '_io', 'Could not extract from defined archive file. ' ) . $ex->getMessage() );
            }
        }

        /**
         * @ignore
         * @since  v0.1
         */
        public static function GetZipArchiveError( $code )
        {
            switch ( $code )
            {
                case \ZipArchive::ER_COMPNOTSUPP:
                    return \UF\_( '_io', 'Zip-Component is not supported.' );
                case \ZipArchive::ER_CRC:
                    return \UF\_( '_io', 'a CRC-Error is occoure.' );
                case \ZipArchive::ER_INCONS:
                    return \UF\_( '_io', 'it results in a inconsistent zip-archive.' );
                case \ZipArchive::ER_INTERNAL:
                    return \UF\_( '_io', 'a internal error (unknown error) is thrown.' );
                case \ZipArchive::ER_MEMORY:
                    return \UF\_( '_io', 'zip-creation needs memory more than usable memorysize.' );
                case \ZipArchive::ER_OPEN:
                    return \UF\_( '_io', 'open the archive results in a error.' );
                case \ZipArchive::ER_WRITE:
                    return \UF\_( '_io', 'writing into archive results in a error.' );
                case \ZipArchive::ER_ZLIB:
                    return \UF\_( '_io', 'a ZLib error (unknown message) is thrown.' );
                default: return \UF\_( '_io', 'a unknown error is thrown.' );
            }
        }

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="- - - -   P R I V A T E   S T A T I C   M E T H O D S   - - - - - - - - - - - - -">

        private static function checkZipSupport( $method )
        {
            if ( !\class_exists('ZipArchive') )
            {
                throw new \UF\MissingExtensionException( 'Zip', $method . \UF\_('_io', ' fails with no ZIP-Support.') );
            }
        }
        private static function zipInitUndo( $zipFile, $owd )
        {
            if ( !\file_exists( $zipFile ) ) { return null; }
            $oldfile = $zipFile . '.old';
            try { self::Move( $zipFile, $oldfile ); return $oldfile; }
            catch ( \Exception $ex ) {
                if ( !\is_null( $owd ) ) { \chdir( $owd ); } throw $ex; }
        }
        private static function zipUndo( $oldfile, $zipFile, $owd )
        {
            if ( !empty($oldfile) ) { self::Move( $oldfile, $zipFile ); }
            if ( !\is_null( $owd ) ) { \chdir( $owd ); }
            throw new \UF\IO\FileAccessException(
                $zipFile, \UF\IO\FileAccessException::ACCESS_CREATE, \UF\_('_io',
                'Zipfile could not be created cause write could not be completed! (Closing file fails)' ) );
        }

        # </editor-fold>

    }

}

